﻿using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;

namespace Socium.Domain
{
	public enum DocumentStreamMode
	{
		Read,
		Write
	}

	public class DatabaseStream : Stream
	{
		readonly GZipStream _zip;
		readonly CryptoStream _crypto;

		public DatabaseStream(Stream stream, DocumentStreamMode mode)
		{
			var formatter = new BinaryFormatter();

			CompressionMode compression_mode;
			CryptoStreamMode crypto_mode;

			var provider = Rijndael.Create();
			ICryptoTransform transform;

			switch (mode)
			{
				case DocumentStreamMode.Read:
					compression_mode = CompressionMode.Decompress;
					crypto_mode = CryptoStreamMode.Read;

					var key = (byte[]) formatter.Deserialize(stream);
					var iv = (byte[]) formatter.Deserialize(stream); 

					transform = provider.CreateDecryptor(key, iv);
					break;

				default:
					compression_mode = CompressionMode.Compress;
					crypto_mode = CryptoStreamMode.Write;

					provider.GenerateKey();
					provider.GenerateIV();

					formatter.Serialize(stream, provider.Key);
					formatter.Serialize(stream, provider.IV); 

					transform = provider.CreateEncryptor();
					break;
			}

			_zip = new GZipStream(stream, compression_mode);
			_crypto = new CryptoStream(_zip, transform, crypto_mode);
		}

		public override void Flush()
		{
			_crypto.FlushFinalBlock();
			_crypto.Flush();
			_zip.Flush();
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			return _crypto.Seek(offset, origin);
		}

		public override void SetLength(long value)
		{
			_crypto.SetLength(value);
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			return _crypto.Read(buffer, offset, count);
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			_crypto.Write(buffer, offset, count);
		}

		public override bool CanRead
		{
			get { return _crypto.CanRead; }
		}

		public override bool CanSeek
		{
			get { return _crypto.CanSeek; }
		}

		public override bool CanWrite
		{
			get { return _crypto.CanWrite; }
		}

		public override long Length
		{
			get { return _crypto.Length; }
		}

		public override long Position
		{
			get { return _zip.Position; }
			set { throw new NotSupportedException(); }
		}

		public override void Close()
		{
			_crypto.Close();
			_zip.Close();
		}

		protected override void Dispose(bool disposing)
		{
            if (disposing) {
                Close();
            }

			base.Dispose(disposing);
		}
	}
}
